package minijava_scanner.scanner;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class Scanner
{
    private Reader inputReader;

    private Map<Character, TerminalSymbol> setOfSymbols;
    private Map<String, TerminalSymbol> setOfOperators;

    private Character currentChar;
    private boolean isEof = false;

    public Scanner(Reader reader)
    {
        inputReader = reader;
        InitSetOfSymbols();
        InitSetOfOperators();
        currentChar = ' ';
    }

    public Token GetNextToken()
    {
        Token token = new Token();
        boolean tokenFound = false;
        while (!tokenFound)
        {
            if (isEof)
            {
                tokenFound = true;
                token.SetId(TerminalSymbol.EOF);
            }
            else
            {
                if (Character.isWhitespace(currentChar))
                {
                    SkipWhitespaces();
                }
                else if (setOfSymbols.containsKey(currentChar))
                {
                    tokenFound = true;
                    token.SetId(setOfSymbols.get(currentChar));
                    GetNextChar();
                }
                else if (Character.isAlphabetic(currentChar))
                {
                    tokenFound = true;
                    String word = GetWord();

                    TerminalSymbol tokenId = (setOfOperators.containsKey(word)) ? setOfOperators.get(word) : TerminalSymbol.IDENTIFIER;
                    token.SetId(tokenId);
                    token.SetData(word);
                }
                else if (Character.isDigit(currentChar))
                {
                    String numericString = GetNumericString();
                    int tokenData = Integer.parseInt(numericString);
                    tokenFound = true;
                    token.SetId(TerminalSymbol.INTEGER_LITERAL);
                    token.SetData(tokenData);
                }
                else if (currentChar == '&')
                {
                    GetNextChar();
                    if (currentChar == '&')
                    {
                        tokenFound = true;
                        token.SetId(TerminalSymbol.AND);
                        GetNextChar();
                    }
                    else
                    {
                        HandleError();
                    }
                }
                else if (currentChar == '/')
                {
                    GetNextChar();
                    if (currentChar == '/')
                    {
                        String data = ReadComments();
                        tokenFound = true;
                        token.SetId(TerminalSymbol.COMMENT);
                        token.SetData(data);
                    }
                    else
                    {
                        HandleError();
                    }
                }
                else
                {
                    HandleError();
                }
            }
        }
        return token;
    }

    private void GetNextChar()
    {
        try
        {
            int ch = inputReader.read();
            isEof = (ch == -1);
            currentChar = (char) ch;
        }
        catch (IOException ex)
        {
            System.out.println(ex.getMessage());
            isEof = true;
        }
    }

    private void HandleError()
    {
        System.out.println("\nUnexpected symbol: " + currentChar);
    }

    private String GetNumericString()
    {
        String numericString = "";
        while (Character.isDigit(currentChar))
        {
            numericString += currentChar;
            GetNextChar();
        }
        if (Character.isAlphabetic(currentChar))
        {
            HandleError();
        }
        return numericString;
    }

    private String GetWord()
    {
        String word = "";
        while (currentChar.toString().matches("[a-zA-Z0-9_]"))
        {
            word += currentChar;
            GetNextChar();
        }
        return word;
    }

    private String ReadComments()
    {
        String commentString = "/";
        while (currentChar != Character.LINE_SEPARATOR)
        {
            commentString += currentChar;
            GetNextChar();
        }
        return commentString;
    }

    private void SkipWhitespaces()
    {
        while (Character.isWhitespace(currentChar))
        {
            System.out.print(currentChar);
            GetNextChar();
        }
    }

    private void InitSetOfSymbols()
    {
        setOfSymbols = new HashMap<Character, TerminalSymbol>();
        setOfSymbols.put('!', TerminalSymbol.EXCLAMATION);
        setOfSymbols.put('<', TerminalSymbol.LESS);
        setOfSymbols.put('=', TerminalSymbol.ASSIGN);
        setOfSymbols.put('+', TerminalSymbol.PLUS);
        setOfSymbols.put('-', TerminalSymbol.MINUS);
        setOfSymbols.put('*', TerminalSymbol.MUL);
        setOfSymbols.put('[', TerminalSymbol.SQUARE_BRACKET_LEFT);
        setOfSymbols.put(']', TerminalSymbol.SQUARE_BRACKET_RIGHT);
        setOfSymbols.put('(', TerminalSymbol.ROUND_BRACKET_LEFT);
        setOfSymbols.put(')', TerminalSymbol.ROUND_BRACKET_RIGHT);
        setOfSymbols.put('{', TerminalSymbol.LEFT_BRACE);
        setOfSymbols.put('}', TerminalSymbol.RIGHT_BRACE);
        setOfSymbols.put(';', TerminalSymbol.SEMICOLON);
        setOfSymbols.put(',', TerminalSymbol.COMMA);
        setOfSymbols.put('.', TerminalSymbol.DOT);
    }

    private void InitSetOfOperators()
    {
        setOfOperators = new HashMap<String, TerminalSymbol>();
        setOfOperators.put("class", TerminalSymbol.CLASS);
        setOfOperators.put("public", TerminalSymbol.PUBLIC);
        setOfOperators.put("static", TerminalSymbol.STATIC);
        setOfOperators.put("void", TerminalSymbol.VOID);
        setOfOperators.put("main", TerminalSymbol.MAIN);
        setOfOperators.put("String", TerminalSymbol.STRING);
        setOfOperators.put("extends", TerminalSymbol.EXTENDS);
        setOfOperators.put("return", TerminalSymbol.RETURN);
        setOfOperators.put("int", TerminalSymbol.INT);
        setOfOperators.put("boolean", TerminalSymbol.BOOLEAN);
        setOfOperators.put("if", TerminalSymbol.IF);
        setOfOperators.put("else", TerminalSymbol.ELSE);
        setOfOperators.put("while", TerminalSymbol.WHILE);
        setOfOperators.put("System", TerminalSymbol.SYSTEM);
        setOfOperators.put("out", TerminalSymbol.OUT);
        setOfOperators.put("println", TerminalSymbol.PRINTLN);
        setOfOperators.put("length", TerminalSymbol.LENGTH);
        setOfOperators.put("true", TerminalSymbol.TRUE);
        setOfOperators.put("false", TerminalSymbol.FALSE);
        setOfOperators.put("this", TerminalSymbol.THIS);
        setOfOperators.put("new", TerminalSymbol.NEW);
    }

}
